package net.lulihu.pangolin.headless.server.handler;


import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import net.lulihu.pangolin.headless.common.container.annotation.Autowired;
import net.lulihu.pangolin.headless.common.container.annotation.Component;
import net.lulihu.pangolin.headless.common.model.AuthAndPenetrate;
import net.lulihu.pangolin.headless.common.model.Penetrate;
import net.lulihu.pangolin.headless.common.model.PenetrateMapping;
import net.lulihu.pangolin.headless.common.protocol.ProtocolMessage;
import net.lulihu.pangolin.headless.common.protocol.ProtocolMessageType;
import net.lulihu.pangolin.headless.common.protocol.handler.RegistrationMessageTypeHandlerComponent;
import net.lulihu.pangolin.headless.common.util.IDGeneratorKit;
import net.lulihu.pangolin.headless.server.component.ServerProxyNetworkComponent;
import net.lulihu.pangolin.headless.server.component.UserProxyNetworkComponent;
import net.lulihu.pangolin.headless.server.config.ProxyServerConfig;
import net.lulihu.pangolin.headless.server.kit.ChannelMappingKit;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;

/**
 * 验证并申请代理端口消息处理程序
 */
@Slf4j
@Component
public class AuthAndApplyForAgentPortMessageHandler implements RegistrationMessageTypeHandlerComponent {

    @Autowired
    private ProxyServerConfig proxyServerConfig;
    @Autowired
    private UserProxyNetworkComponent userProxyNetworkComponent;

    @Override
    public byte messageType() {
        return ProtocolMessageType.C_AUTH_AND_PENETRATE;
    }

    @Override
    public void handle(ChannelHandlerContext context, ProtocolMessage message) {
        if (log.isDebugEnabled()) log.debug("收到客户端认证并申请穿透地址消息");

        Channel channel = context.channel();

        AuthAndPenetrate authAndPenetrate = message.getBodyObj(AuthAndPenetrate.class);

        // 秘钥认证
        if (!authSecret(authAndPenetrate.getSecret())) {
            // 认证失败 关闭连接
            channel.writeAndFlush(wrongKey());
            channel.close();

            if (log.isDebugEnabled()) log.debug("客户端认证失败，通道关闭");
            return;
        }

        // 申请映射端口处理
        List<Penetrate> penetrates = authAndPenetrate.getPenetrates();
        List<PenetrateMapping> mappings = new ArrayList<>(penetrates.size());

        for (Penetrate penetrate : penetrates) {
            PenetrateMapping mapping = new PenetrateMapping();
            mapping.setAddress(penetrate.getAddress());
            try {
                String mappingId = penetrate.getMappingId();
                Integer mappingPort = ChannelMappingKit.getMappingPort(mappingId);
                //未映射过  则绑定监听端口 生成映射Id
                if (mappingPort == null) {
                    mappingPort = userProxyNetworkComponent.bindPort(0);
                    mappingId = IDGeneratorKit.getStr();
                }

                // 添加映射
                ChannelMappingKit.addMapping(mappingId, mappingPort, penetrate.getAddress(), channel);

                mapping.setMappingPort(mappingPort);
                mapping.setMappingId(mappingId);
            } catch (Exception e) {
                mapping.setFailureReason(e.getMessage());
                log.warn("客户端申请端口代理时出现例外", e);
            }
            mappings.add(mapping);
        }
        // 发送端口映射消息
        ProtocolMessage portMappingResult = new ProtocolMessage();
        portMappingResult.setType(ProtocolMessageType.S_PENETRATE);
        portMappingResult.setBodyObj(mappings);
        channel.writeAndFlush(portMappingResult);

        if (log.isDebugEnabled()) log.debug("客户端穿透映射端口消息发送成功");
    }

    /**
     * 获取可用端口
     *
     * @return 端口
     * @throws IOException 没有可用端口
     */
    private Integer getAvailablePort() throws IOException {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(0);
            return serverSocket.getLocalPort();
        } finally {
            if (serverSocket != null)
                serverSocket.close();
        }
    }


    /**
     * 错误的秘钥返回参数
     *
     * @return {@linkplain ProtocolMessage}
     */
    private ProtocolMessage wrongKey() {
        ProtocolMessage wrongKey = new ProtocolMessage();
        wrongKey.setType(ProtocolMessageType.S_AUTH_FAIL);
        wrongKey.setBodyStr("错误的认证秘钥");
        return wrongKey;
    }

    /**
     * 秘钥认证
     *
     * @param authSecret 被认证的秘钥
     * @return true认证成功反之失败
     */
    private boolean authSecret(String authSecret) {
        String secret = proxyServerConfig.getSecret();
        return secret.equals(authSecret);
    }
}
